#include "node_watchdog.h"
#include "node_internals.h"
#include "util.h"
#include "util-inl.h"
#include <algorithm>

namespace node {

Watchdog::Watchdog(v8::Isolate* isolate, uint64_t ms)
    : isolate_(isolate)
    , timed_out_(false)
    , destroyed_(false)
{
    int rc;
    loop_ = new uv_loop_t;
    NODE_CHECK(loop_);
    rc = uv_loop_init(loop_);
    if (rc != 0) {
        FatalError("node::Watchdog::Watchdog()",
            "Failed to initialize uv loop.");
    }

    rc = uv_async_init(loop_, &async_, &Watchdog::Async);
    NODE_CHECK_EQ(0, rc);

    rc = uv_timer_init(loop_, &timer_);
    NODE_CHECK_EQ(0, rc);

    rc = uv_timer_start(&timer_, &Watchdog::Timer, ms, 0);
    NODE_CHECK_EQ(0, rc);

    rc = uv_thread_create(&thread_, &Watchdog::Run, this);
    NODE_CHECK_EQ(0, rc);
}

Watchdog::~Watchdog()
{
    Destroy();
}

void Watchdog::Dispose()
{
    Destroy();
}

void Watchdog::Destroy()
{
    if (destroyed_) {
        return;
    }

    uv_async_send(&async_);
    uv_thread_join(&thread_);

    uv_close(reinterpret_cast<uv_handle_t*>(&async_), nullptr);

    // UV_RUN_DEFAULT so that libuv has a chance to clean up.
    uv_run(loop_, UV_RUN_DEFAULT);

    int rc = uv_loop_close(loop_);
    NODE_CHECK_EQ(0, rc);
    delete loop_;
    loop_ = nullptr;

    destroyed_ = true;
}

void Watchdog::Run(void* arg)
{
    Watchdog* wd = static_cast<Watchdog*>(arg);

    // UV_RUN_DEFAULT the loop will be stopped either by the async or the
    // timer handle.
    uv_run(wd->loop_, UV_RUN_DEFAULT);

    // Loop ref count reaches zero when both handles are closed.
    // Close the timer handle on this side and let Destroy() close async_
    uv_close(reinterpret_cast<uv_handle_t*>(&wd->timer_), nullptr);
}

void Watchdog::Async(uv_async_t* async)
{
    Watchdog* w = ContainerOf(&Watchdog::async_, async);
    uv_stop(w->loop_);
}

void Watchdog::Timer(uv_timer_t* timer)
{
    Watchdog* w = ContainerOf(&Watchdog::timer_, timer);
    w->timed_out_ = true;
    uv_stop(w->loop_);
    w->isolate()->TerminateExecution();
}

SigintWatchdog::~SigintWatchdog()
{
    Destroy();
}

void SigintWatchdog::Dispose()
{
    Destroy();
}

SigintWatchdog::SigintWatchdog(v8::Isolate* isolate)
    : isolate_(isolate)
    , received_signal_(false)
    , destroyed_(false)
{
    // Register this watchdog with the global SIGINT/Ctrl+C listener.
    SigintWatchdogHelper::GetInstance()->Register(this);
    // Start the helper thread, if that has not already happened.
    SigintWatchdogHelper::GetInstance()->Start();
}

void SigintWatchdog::Destroy()
{
    if (destroyed_) {
        return;
    }

    destroyed_ = true;

    SigintWatchdogHelper::GetInstance()->Unregister(this);
    SigintWatchdogHelper::GetInstance()->Stop();
}

void SigintWatchdog::HandleSigint()
{
    received_signal_ = true;
    isolate_->TerminateExecution();
}

#ifdef __POSIX__
void* SigintWatchdogHelper::RunSigintWatchdog(void* arg)
{
    // Inside the helper thread.
    bool is_stopping;

    do {
        uv_sem_wait(&instance.sem_);
        is_stopping = InformWatchdogsAboutSignal();
    } while (!is_stopping);

    return nullptr;
}

void SigintWatchdogHelper::HandleSignal(int signum)
{
    uv_sem_post(&instance.sem_);
}

#else

// Windows starts a separate thread for executing the handler, so no extra
// helper thread is required.
BOOL WINAPI SigintWatchdogHelper::WinCtrlCHandlerRoutine(DWORD dwCtrlType)
{
    if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) {
        InformWatchdogsAboutSignal();

        // Return true because the signal has been handled.
        return TRUE;
    } else {
        return FALSE;
    }
}
#endif

bool SigintWatchdogHelper::InformWatchdogsAboutSignal()
{
    Mutex::ScopedLock list_lock(instance.list_mutex_);

    bool is_stopping = false;
#ifdef __POSIX__
    is_stopping = instance.stopping_;
#endif

    // If there are no listeners and the helper thread has been awoken by a signal
    // (= not when stopping it), indicate that by setting has_pending_signal_.
    if (instance.watchdogs_.empty() && !is_stopping) {
        instance.has_pending_signal_ = true;
    }

    for (auto it : instance.watchdogs_)
        it->HandleSigint();

    return is_stopping;
}

int SigintWatchdogHelper::Start()
{
    Mutex::ScopedLock lock(mutex_);

    if (start_stop_count_++ > 0) {
        return 0;
    }

#ifdef __POSIX__
    NODE_CHECK_EQ(has_running_thread_, false);
    has_pending_signal_ = false;
    stopping_ = false;

    sigset_t sigmask;
    sigfillset(&sigmask);
    NODE_CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask));
    int ret = pthread_create(&thread_, nullptr, RunSigintWatchdog, nullptr);
    NODE_CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr));
    if (ret != 0) {
        return ret;
    }
    has_running_thread_ = true;

    RegisterSignalHandler(SIGINT, HandleSignal);
#else
    SetConsoleCtrlHandler(WinCtrlCHandlerRoutine, TRUE);
#endif

    return 0;
}

bool SigintWatchdogHelper::Stop()
{
    bool had_pending_signal;
    Mutex::ScopedLock lock(mutex_);

    {
        Mutex::ScopedLock list_lock(list_mutex_);

        had_pending_signal = has_pending_signal_;

        if (--start_stop_count_ > 0) {
            has_pending_signal_ = false;
            return had_pending_signal;
        }

#ifdef __POSIX__
        // Set stopping now because it's only protected by list_mutex_.
        stopping_ = true;
#endif

        watchdogs_.clear();
    }

#ifdef __POSIX__
    if (!has_running_thread_) {
        has_pending_signal_ = false;
        return had_pending_signal;
    }

    // Wake up the helper thread.
    uv_sem_post(&sem_);

    // Wait for the helper thread to finish.
    NODE_CHECK_EQ(0, pthread_join(thread_, nullptr));
    has_running_thread_ = false;

    RegisterSignalHandler(SIGINT, SignalExit, true);
#else
    SetConsoleCtrlHandler(WinCtrlCHandlerRoutine, FALSE);
#endif

    had_pending_signal = has_pending_signal_;
    has_pending_signal_ = false;

    return had_pending_signal;
}

bool SigintWatchdogHelper::HasPendingSignal()
{
    Mutex::ScopedLock lock(list_mutex_);

    return has_pending_signal_;
}

void SigintWatchdogHelper::Register(SigintWatchdog* wd)
{
    Mutex::ScopedLock lock(list_mutex_);

    watchdogs_.push_back(wd);
}

void SigintWatchdogHelper::Unregister(SigintWatchdog* wd)
{
    Mutex::ScopedLock lock(list_mutex_);

    auto it = std::find(watchdogs_.begin(), watchdogs_.end(), wd);

    NODE_CHECK_NE(it, watchdogs_.end());
    watchdogs_.erase(it);
}

SigintWatchdogHelper::SigintWatchdogHelper()
    : start_stop_count_(0)
    , has_pending_signal_(false)
{
#ifdef __POSIX__
    has_running_thread_ = false;
    stopping_ = false;
    NODE_CHECK_EQ(0, uv_sem_init(&sem_, 0));
#endif
}

SigintWatchdogHelper::~SigintWatchdogHelper()
{
    start_stop_count_ = 0;
    Stop();

#ifdef __POSIX__
    NODE_CHECK_EQ(has_running_thread_, false);
    uv_sem_destroy(&sem_);
#endif
}

SigintWatchdogHelper SigintWatchdogHelper::instance;

} // namespace node
